#include <stdio.h>
#include <errno.h>
#include <signal.h>
#include <unistd.h>
#include <assert.h>
#include <stdint.h>

#include <anet/util.h>
#include <anet/anet.h>
#include <anet/ae.h>

#include "config.h"
#include "env.h"
#include "client.h"
#include "buffer.h"
#include "packet.h"
#include "game.h"

struct Env* global_env = NULL;

static int HandlePacket(Client* client, Packet* packet);
static int HandleJoinInPacket(Client* client, Packet* packet);
static int HandleLeavePacket(Client* client, Packet* packet);
static int HandleDoActionPacket(Client* client, Packet* packet);

static int ResponseMessagePacket(Client* client, uint32_t id, const char* data, int length);

static void WriteTcpHandler(aeEventLoop* el, int fd, void* privdata, int mask) {
    //LogInfo("enter write tcp handler");
    Client* client = (Client *)privdata;

    Buffer* b = client->output;
    assert(b->offset < b->length);

    while (1) {
        int len = write(client->fd, b->data + b->offset, b->length - b->offset);
        if (len < 0) {
            if (errno == EAGAIN || errno == EINTR) break;
            LogError("read fail: %d:%s", errno, strerror(errno));
            goto ERR;
        } else if (len == 0) {
            LogInfo("WRITE: remote closed connection");
            goto ERR;
        }
        b->offset += len;
        if (b->offset == b->length) {
            aeDeleteFileEvent(el, fd, AE_WRITABLE);
            BufferShift(b);
            break;
        }
    }

    //LogInfo("exit write tcp handler");
    return;
ERR:
    aeDeleteFileEvent(el, fd, mask);
    global_env->Leave(global_env, client);
    global_env->RemoveClient(global_env, client);
    //LogInfo("exit write tcp handler");
}

static void ReadTcpHandler(aeEventLoop* el, int fd, void* privdata, int mask) {
    //LogInfo("enter read tcp handler");
    Client* client = (Client *)privdata;

    Buffer* b = client->input;
    assert(b->length != b->capacity);

    while (1) {
        int len = read(fd, b->data + b->length, b->capacity - b->length);
        if (len < 0) {
            if (errno == EAGAIN || errno == EINTR) {
              len = 0;
              break;
            }
            LogError("read fail: %d:%s", errno, strerror(errno));
            goto ERR;
        } else if (len == 0) {
            LogInfo("READ: remote closed connection");
            goto ERR;
        }
        b->length += len;
        while ((size_t)(b->length - b->offset) >= sizeof(Packet)) {
            Packet* packet = (Packet *)(b->data + b->offset);
            if ((size_t)(b->length - b->offset) >= sizeof(Packet) + packet->length) {
                int s = HandlePacket(client, packet);
                if (s < 0) {
                    LogError("handle packet fail");
                    goto ERR;
                }
                b->offset += sizeof(Packet) + packet->length;
            } else {
              break;
            }
        }
        if (b->length == b->capacity) {
            if (b->offset > 0) {
                BufferShift(b);
            } else {
                BufferResize(b, 0);
            }
        }
    }
    return;
ERR:
    aeDeleteFileEvent(el, fd, mask);
    global_env->Leave(global_env, client);
    global_env->RemoveClient(global_env, client);
}

static void AcceptTcpHandler(aeEventLoop* el, int fd, void* privdata, int mask) {
    UNUSED(privdata);
    UNUSED(mask);

    char err[ANET_ERR_LEN];

    int client_port;
    char client_ip[128];
    int clientfd = anetTcpAccept(err, fd, client_ip, &client_port);
    if (clientfd == AE_ERR) {
        LogError("Accept client connection failed: %s", err);
        return;
    }

    LogInfo("Accept client %s:%d", client_ip, client_port);

    anetNonBlock(NULL, clientfd);
    anetTcpNoDelay(NULL, clientfd);

    Client* client = ClientCreate(el, clientfd, NULL); 
    if (client == NULL || aeCreateFileEvent(el, clientfd, AE_READABLE, ReadTcpHandler, client) == AE_ERR) {
        LogError("add client %s:%d to event loop fail", client_ip, client_port);
        anetClose(clientfd);
        return;
    }
    global_env->AddClient(global_env, client);
}

static void SignalHandler(int signo) {
    if (signo == SIGINT || signo == SIGTERM) {
        if (global_env && global_env->el) {
            global_env->el->stop = 1;
        }
    }
}

int main(int argc, char** argv) {
    char err[ANET_ERR_LEN];
    memset(err, 0, ANET_ERR_LEN);

    global_env = EnvParseArgs(argc, argv);

    if (global_env->run_daemonize) Daemonize();

    struct sigaction sig_action;
    sig_action.sa_handler = SignalHandler;
    sig_action.sa_flags = 0;
    sigemptyset(&sig_action.sa_mask);
    sigaction(SIGINT, &sig_action, NULL);
    sigaction(SIGTERM, &sig_action, NULL);
    sigaction(SIGPIPE, &sig_action, NULL);

    global_env->el = aeCreateEventLoop(65536);

    global_env->listenfd = anetTcpServer(err, global_env->port, global_env->host);
    if (global_env->listenfd < 0 || aeCreateFileEvent(global_env->el, global_env->listenfd, AE_READABLE, AcceptTcpHandler, NULL) == AE_ERR) {
        aeDeleteEventLoop(global_env->el);
        EnvDestroy(global_env);
        LogFatal("listen failed: %s", global_env->listenfd < 0 ? err : strerror(errno));
    }
    anetNonBlock(NULL, global_env->listenfd);
    anetTcpNoDelay(NULL, global_env->listenfd);
    LogInfo("listen on %s:%d", global_env->host, global_env->port);

    aeMain(global_env->el);

    LogInfo("Byebye");

    aeDeleteEventLoop(global_env->el);
    EnvDestroy(global_env);

	return 0;
}

static int HandlePacket(Client* client, Packet* packet) {
    if (packet->magic != MAGIC) return -1;
    switch (packet->command) {
        case COMMAND_JOIN_IN: return HandleJoinInPacket(client, packet);
        case COMMAND_LEAVE: return HandleLeavePacket(client, packet);
        case COMMAND_DO_ACTION: return HandleDoActionPacket(client, packet);
    }

    ResponseMessagePacket(client, packet->id, "OK", 2);
    LogError("error packet command %d", packet->command);
    return -1;
}

static int HandleJoinInPacket(Client* client, Packet* packet) {
    LogInfo("handle join in packet");
    Game* game = global_env->AutoJoinIn(global_env, client);
    char* b = NULL;
    int length = GameToBuffer(game, &b);
    ResponseMessagePacket(client, packet->id, b, length);
    if (game) {
      if (game->hero && client != game->hero) {
        ResponseMessagePacket(game->hero, packet->id, b, length); 
      }
      if (game->sword && client != game->sword) {
        ResponseMessagePacket(game->sword, packet->id, b, length);
      }
      if (game->boss && client != game->boss) {
        ResponseMessagePacket(game->boss, packet->id, b, length);
      }
    }
    free(b);
    return 0;
}

static int HandleLeavePacket(Client* client, Packet* packet) {
    LogInfo("handle leave packet");
    Game* game = global_env->Leave(global_env, client);
    char* b = NULL;
    int length = GameToBuffer(game, &b);
    ResponseMessagePacket(client, packet->id, b, length);
    if (game) {
      if (game->hero && client != game->hero) {
        ResponseMessagePacket(game->hero, packet->id, b, length); 
      }
      if (game->sword && client != game->sword) {
        ResponseMessagePacket(game->sword, packet->id, b, length);
      }
      if (game->boss && client != game->boss) {
        ResponseMessagePacket(game->boss, packet->id, b, length);
      }
    }
    free(b);
    return 0;
}

static int HandleDoActionPacket(Client* client, Packet* packet) {
    LogInfo("handle do action packet");
    int action = *(int *)packet->data;
    Game* game = global_env->DoAction(global_env, client, action);
    char* b = NULL;
    int length = GameToBuffer(game, &b);
    ResponseMessagePacket(client, packet->id, b, length);
    if (game) {
      if (game->hero && client != game->hero) {
        LogInfo("action hero");
        ResponseMessagePacket(game->hero, packet->id, b, length); 
      } else {
        LogInfo("!action hero");
      }
      if (game->sword && client != game->sword) {
        LogInfo("action sword");
        ResponseMessagePacket(game->sword, packet->id, b, length);
      } else {
        LogInfo("!action sword");
      }
      if (game->boss && client != game->boss) {
        LogInfo("action boss");
        ResponseMessagePacket(game->boss, packet->id, b, length);
      } else {
        LogInfo("!action boss");
      }
      if (GameIsPreparedToJudge(game)) {
        GameReset(game);
      }
    }
    free(b);
    return 0;
}

static int ResponseMessagePacket(Client* client, uint32_t id, const char* data, int length) {
    if (!BufferCanContains(client->output, length + sizeof(Packet))) {
        BufferShift(client->output);
        while (!BufferCanContains(client->output, length + sizeof(Packet))) {
            BufferResize(client->output, 0);
        }
    }

    Packet* packet = (Packet *)(client->output->data + client->output->length);
    packet->magic = MAGIC;
    packet->command = COMMAND_MESSAGE;
    packet->id = id;
    packet->length = length;
    memcpy(packet->data, data, length);

    client->output->length += length + sizeof(Packet);

    int flags = aeCreateFileEvent(client->el, client->fd, AE_WRITABLE, WriteTcpHandler, client);
    UNUSED(flags);
    assert(flags != AE_ERR);

    return 0;
}

